package sketch.ounit.fuzz;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import randoop.ComponentManager;
import randoop.ExecutableSequence;
import randoop.RMethod;
import randoop.Sequence;
import randoop.StatementKind;
import randoop.main.GenInputsAbstract;
import randoop.main.GenTests;
import randoop.util.Reflection;
import randoop.util.SimpleList;
import sketch.specs.RandomSequenceCreator;
import sketch.util.Checker;
import sketch.util.Randomness;
import sketch.util.SketchLog;

/**
 * XXX this class should be gradually deprecaded
 * */
public class ExistingObjectFuzzer {
	
	public static boolean print_sequence = false;
	
	/**
	 * time_limit and its set up method
	 * */
	static int time_limit = 0;
	public static void setTimeLimit(int time) {
		Checker.checkTrue(time > 0, "The time limit: " + time  + " should > 0");
		time_limit = time;
	}
	
	/**
	 * Cached method list for each class, and the method to look up
	 * method list
	 * */
	private static Map<Class<?>, List<RMethod>> clazz_methods = new LinkedHashMap<Class<?>, List<RMethod>>();
	private static List<RMethod> getRMethodList(Class<?> clazz) {
		if(clazz_methods.containsKey(clazz)) {
			return clazz_methods.get(clazz);
		}
		Collection<Class<?>> coll = new LinkedList<Class<?>>();
		coll.add(clazz);
		List<StatementKind> statements = Reflection.getStatements(coll, null);
		List<RMethod> rmethods = new LinkedList<RMethod>();
		for(StatementKind statement : statements) {
			if(statement instanceof RMethod) {
				rmethods.add((RMethod)statement);
			}
		}
		clazz_methods.put(clazz, rmethods);
		return rmethods;
	}
	
	//data structure for storing all pre-created pool
	//1. how to retrive fast
	//2. classify existing objects
	//the components datastructure here is used for fast lookup
	private static ComponentManager components = null;
	static void generate_object_pool(List<Class<?>> classes) {
		Checker.checkNull(classes, "The class collection could not be null.");
		Checker.checkNull(!classes.isEmpty(), "The class collection could not be empty.");
		initializePool(classes.get(0), (Class<?>[])classes.toArray(new Class<?>[0]));
	}
	static boolean hasInitializePool() {
		return components != null;
	}
	private static void initializePool(Class<?> mainClass, Class<?>... classes) {
		RandomSequenceCreator creator = new RandomSequenceCreator(mainClass, classes);
		creator.generateSequences(time_limit);
		components = creator.getComponentManagerInRandoop();
	}
	
	public static void outputRandoopGeneratedTests() {
		List<ExecutableSequence> eseq_list = new LinkedList<ExecutableSequence>();
		for(Sequence seq : components.getAllGeneratedSequences()) {
			ExecutableSequence eseq = new ExecutableSequence(seq);
			eseq.execute(null);
			eseq_list.add(eseq);
		}
		GenTests.write_junit_tests(GenInputsAbstract.junit_output_dir , eseq_list, null);
	}
	
	private static List<AssembledSequenceObjectMutation<?>> current_gen_list = new LinkedList<AssembledSequenceObjectMutation<?>>();
	public static List<AssembledSequenceObjectMutation<?>> getAndClearCurrentGeneratedSequences() {
		List<AssembledSequenceObjectMutation<?>> ret_sequence = getCurrentGeneratedSequence();
		clearCurrentGeneratedSequence();
		return ret_sequence;
	}
	
	public static List<AssembledSequenceObjectMutation<?>> getCurrentGeneratedSequence() {
		List<AssembledSequenceObjectMutation<?>> ret_sequence = new LinkedList<AssembledSequenceObjectMutation<?>>();
		for(AssembledSequenceObjectMutation<?> seq : current_gen_list) {
			ret_sequence.add(seq);
		}
		return ret_sequence;
	}
	
	public static void clearCurrentGeneratedSequence() {
		current_gen_list.clear();
	}
	
	private static Collection<AssembledSequenceObjectMutation<?>> pool = new HashSet<AssembledSequenceObjectMutation<?>>();
	private static<T> T fuzzObjectUsingPool(T t, Class<T> mainClass) {
		Checker.checkTrue(hasInitializePool(), "The pool should be initialized.");
		List<RMethod> rmethods = getRMethodList(mainClass);
		if(rmethods.isEmpty()) {
			return t;
		}
		//XXX also store the generated sequence text
		AssembledSequenceObjectMutation<T> assembled_sequence = new AssembledSequenceObjectMutation<T>(rmethods, t, mainClass, components);
		assembled_sequence.execute_sequence();
		if(print_sequence) {
		  System.out.println("\n\nThe sequence is: ");
		  System.out.println(assembled_sequence.toCodeString("%receiver%"));
		}
		if(pool.contains(assembled_sequence)) {
			//System.out.println("   -- redundant");
			assembled_sequence.makeItRedundant();
		} else {
			pool.add(assembled_sequence);
		}
		
		current_gen_list.add(assembled_sequence);
		
		return assembled_sequence.getResultObject();
	}
	private static<T> T fuzzObjectWithArgPool(T t, Class<T> mainClass, Object...args) {
		Checker.checkTrue(hasInitializePool(), "The pool should be initialized.");
		List<RMethod> rmethods = getRMethodList(mainClass);
		if(rmethods.isEmpty()) {
			return t;
		}
		//XXX also store the generated sequence text
		AssembledSequenceObjectMutationWithArgs<T> assembled_sequence = new AssembledSequenceObjectMutationWithArgs<T>(rmethods, t, mainClass, components, args);
		assembled_sequence.execute_sequence();
		if(print_sequence) {
		  System.out.println("\n\nThe sequence is: ");
		  System.out.println(assembled_sequence.toString());
		}
		if(pool.contains(assembled_sequence)) {
			//System.out.println("   -- redundant");
			assembled_sequence.makeItRedundant();
		} else {
			pool.add(assembled_sequence);
		}
		
		current_gen_list.add(assembled_sequence);
		
		return assembled_sequence.getResultObject();
	}
	
	public static<T> T fuzzObject(T t, Class<T> mainClass, Object...args) {
		if(!hasInitializePool()) {
			System.err.println("The pool has not been initialized, but we just skip it!");
		}
		List<RMethod> rmethods = getRMethodList(mainClass);
		if(rmethods.isEmpty()) {
			return t;
		}
		return fuzzObjectWithArgPool(t, mainClass, args);
	}
	
	public static<T> T fuzzObject(T t, Class<T> mainClass, Class<?>...classes) {
		Checker.checkNull(t, "The object to fuzz could not be null!");
		if(!hasInitializePool()) {
			System.err.println("start to initialize the object pool.");
			initializePool(mainClass, classes);
		}
		//get the rmethod list
		List<RMethod> rmethods = getRMethodList(mainClass);
		if(rmethods.isEmpty()) {
			return t;
		}
		return fuzzObjectUsingPool(t, mainClass);
	}
	
	public static void clear_state() {
		print_sequence = false;
		time_limit = 0;
		clazz_methods.clear();
		components = null;
		current_gen_list.clear();
		pool.clear();
	}
}